﻿using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using System.Threading;

namespace LucyGrid
{
	public class GridUtils : IDisposable
	{
		public enum LogType
		{
			NONE = 0,
			CONSOLE = 1,
			TOFILE = 2,
			DEBUG = 4,
			INFO = 16,
			WARNING = 32,
			ERROR = 64,
			ALL = 64 | 32 | 16 | 4
		}

		/// <summary>
		/// Type of node. 
		/// </summary>
		public enum ActorType
		{
			MASTER,
			/// <summary>
			/// Grid node service
			/// </summary>
			GRIDNODE,
			/// <summary>
			/// Grid processor.
			/// </summary>
			WORKER
		}

		/// <summary>
		/// Type of node
		/// </summary>
		static public ActorType Mode = ActorType.MASTER;


		public static int EnableLog = (int)LogType.TOFILE | (int)LogType.ALL;
		public static Action<string, string> LogCustomOutput = null;
		public static FileStream LogFile { get; set; }
		public static ConcurrentQueue<string> msgQueue;
		private static bool finalize = false;
		private static Thread msgQueuedThred { get; set; }
		static GridUtils()
		{
			StartMessageQueue();
		}

		static void InitLog()
		{
			var logdir = LogPath();
			if (!System.IO.Directory.Exists(logdir))
				System.IO.Directory.CreateDirectory(logdir);
			var logFile = string.Format("{0}/{1}_log.{2}.txt",
				logdir,
				Mode,
				Process.GetCurrentProcess().Id);
			LogFile = File.Open(logFile, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
		}

		private static void StartMessageQueue()
		{
			msgQueue = new ConcurrentQueue<string>();
			msgQueuedThred = GridTask.Run(() =>
			{
				Thread.CurrentThread.Name = string.Format("GRIDUTILS MESSGAEQUEU-{0}", Thread.CurrentThread.ManagedThreadId);
				string msg = null;
				while (!finalize)
				{
					while (msgQueue.TryDequeue(out msg))
					{
						if ((EnableLog & (int)LogType.DEBUG) != 0)
							System.Diagnostics.Debug.WriteLine(msg);
						if ((EnableLog & (int)LogType.CONSOLE) != 0)
							lock (System.Console.Out)
								System.Console.WriteLine(msg);
						if ((EnableLog & (int)LogType.TOFILE) != 0)
							WriteLog(msg);
					}
					Thread.Sleep(100);
				}
			});
		}

		public static string JsonSerialize(object data)
		{
			return JsonConvert.SerializeObject(data);
		}

		public static object JsonDeserialize(string data)
		{
			return JsonConvert.DeserializeObject(data);
		}

		public static BinaryFormatter binSerializer = new BinaryFormatter();

		/// <summary>
		/// Serializer helper. Depnding on the data type uses different serializations.
		/// </summary>
		/// <param name="s"></param>
		/// <param name="data"></param>
		public static void Serialize(Stream s, params object[] data)
		{
			if (data == null)
				return;
			// binSerializer.Serialize(s, data);
			/*using (var compressionStream = new GZipStream(
									 s, CompressionMode.Compress, true ))
			{*/
			var compressionStream = s;
				foreach (object o in data)
				{
					var t = o.GetType();
					var isBin = IsSerializable(t);
					if (isBin)
						SerializeBinary(compressionStream, o);
					else
						SerializeCustom(compressionStream, o);
				}
				compressionStream.Flush();
			/*}*/
		}

		private static bool IsSerializable(Type t)
		{
			if (!t.IsArray && t.IsSerializable)
				return true;
			if (t.IsArray && IsSerializable(t.GetElementType()))
				return true;
			return false;
		}

		/// <summary>
		/// .NET binary serialization
		/// </summary>
		/// <param name="s"></param>
		/// <param name="data"></param>
		public static void SerializeBinary(Stream s, object data)
		{
			binSerializer.Serialize(s, data);
		}

		/// <summary>
		/// Serializes custom/anonymous types
		/// </summary>
		/// <param name="s"></param>
		/// <param name="data"></param>
		public static void SerializeCustom(Stream s, object data)
		{
			var binwriter = new BinaryWriter(s, UTF8Encoding.UTF8, true /*leave open*/);
			var json = Newtonsoft.Json.JsonConvert.SerializeObject(data);
			var bytes = UTF8Encoding.UTF8.GetBytes(json);
			s.Write(bytes, 0, bytes.Length);
			// Do not call dispose to avoid closing stream
			binwriter.Flush();
		}
		/// <summary>
		/// Deserializer helper. Depending on the object type uses different serializers
		/// </summary>
		/// <param name="s"></param>
		/// <param name="targetType"></param>
		/// <returns></returns>
		public static List<object> Deserialize(Stream s, params Type[] targetTypes)
		{
			List<object> res = new List<object>(targetTypes.Length);
			//return binSerializer.Deserialize(s);
			/*using (var compressionStream = new GZipStream(
									 s, CompressionMode.Decompress, true))
			{*/
			var compressionStream = s;
				foreach (Type targetType in targetTypes)
				{
					if (IsSerializable(targetType))
						res.Add(DeserializeBinary(compressionStream, targetType));
					else
						res.Add(DeserializeCustom(compressionStream, targetType));
				}
			//}
			return res;
		}

		/// <summary>
		/// .NET binary deserialization
		/// </summary>
		/// <param name="s"></param>
		/// <param name="targetType"></param>
		/// <returns></returns>
		public static Object DeserializeBinary(Stream s, Type targetType)
		{
			return binSerializer.Deserialize(s);
		}
		/// <summary>
		/// Deserialize custom/anonymous types
		/// </summary>
		/// <param name="s"></param>
		/// <param name="targetType"></param>
		/// <returns></returns>
		public static Object DeserializeCustom(Stream s, Type targetType)
		{
			JToken jobj = null;
			/*using (var bson = new Newtonsoft.Json.Bson.BsonReader(s))
			{
				jobj = (JObject)Newtonsoft.Json.JsonSerializer.Create(new JsonSerializerSettings() { 
					 
				}).Deserialize(bson);
			}*/
			jobj = (JToken)Newtonsoft.Json.JsonSerializer.Create(new JsonSerializerSettings()
			{

			}).Deserialize(new JsonTextReader(new StreamReader(s, UTF8Encoding.UTF8, false, 1024, true)));
			return FromJToken(jobj, targetType);
		}

		public static Object CreateObjectFromDynamic(Type targeType, JToken jobj)
		{

			var newObject = FormatterServices.GetUninitializedObject(targeType);
			if (jobj != null)
				targeType.GetMembers(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.Static)
					.All(m =>
					{
						FieldInfo fi = null;
						PropertyInfo pi = null;
						Type t = null;
						switch (m.MemberType)
						{
							case MemberTypes.Field:
								fi = ((FieldInfo)m);
								t = fi.FieldType;
								break;
							case MemberTypes.Property:
								pi = ((PropertyInfo)m);
								t = pi.PropertyType;
								break;
							default:
								return true;
						}
						if (fi == null && (pi == null || !pi.CanWrite))
							return true;

						object value = null;
						string jname = m.Name;
						// Dirty....
						if (jname.Contains("<"))
						{
							jname = System.Text.RegularExpressions.Regex.Match(jname, ".*<([^>]*).*").Groups[1].Value;
						}

						value = FromJToken(jobj[jname], t);
						if (fi != null) fi.SetValue(newObject, Convert.ChangeType(value, fi.FieldType));
						if (pi != null && pi.CanWrite) pi.SetValue(newObject, Convert.ChangeType(value, pi.PropertyType), null);
						return true;
					});
			return newObject;
		}

		private static object FromJToken(JToken jtk, Type t)
		{
			object value = null;
			// Null
			if (!"Null".Equals(jtk.Type.ToString()))
			{

				if (isPrimitive(t) || t.IsArray)
				{
					if (!t.IsArray || isPrimitive(t.GetElementType()))
					{
						value = jtk == null ? null : ConvertJObject(jtk, t);
					}
					else
					{
						// Array of complex objects
						var arr = ((JArray)jtk).ToArray()
							.Select(v => Convert.ChangeType(CreateObjectFromDynamic(t.GetElementType(), v), t.GetElementType()))
							.ToArray();
						value = Array.CreateInstance(t.GetElementType(), arr.Length);
						Array.Copy(arr, (Array)value, arr.Length);
					}
				}
				else
				{
					value = CreateObjectFromDynamic(t, jtk);
				}
			};
			return value;
		}

		private static bool isPrimitive(Type t)
		{
			return t.IsPrimitive || t.Equals(typeof(string)) || t.Equals(typeof(DateTime));
		}

		private static object ConvertJObject(JToken jToken, Type targetType)
		{
			if (targetType.IsArray && targetType.GetInterfaces()
					   .Any(x => x == typeof(IEnumerable)))
			{
				return ((JArray)jToken).ToObject(targetType);
			}
			return Convert.ChangeType(jToken, targetType);
		}

		public static void NotifyLog(String tags, String format, params object[] values)
		{
			if (EnableLog == 0)
				return;
			if (tags.Contains("INFO") && (EnableLog & (int)LogType.ALL) +
				(EnableLog & (int)LogType.INFO) == 0)
				return;
			if (tags.Contains("WARNING") && (EnableLog & (int)LogType.ALL) +
				(EnableLog & (int)LogType.WARNING) == 0)
				return;
			if (tags.Contains("ERROR") && (EnableLog & (int)LogType.ALL) +
				(EnableLog & (int)LogType.ERROR) == 0)
				return;
			if (tags.Contains("DEBUG") && (EnableLog & (int)LogType.ALL) +
				(EnableLog & (int)LogType.DEBUG) == 0)
				return;

			var msg = string.Format("[{0}]-{1}-{2}: {3} {4}", DateTime.Now.ToString("HH:mm:ss.fff"),
					Environment.MachineName,
					string.Format("{0}:{1}", Process.GetCurrentProcess().Handle,
						Thread.CurrentThread.ManagedThreadId),
					tags,
					values.Length != 0 ? string.Format(format, values) : format);

			msgQueue.Enqueue(msg);

			if (LogCustomOutput != null)
				LogCustomOutput(tags, msg);
		}


		private static void WriteLog(string msg)
		{
			var data = UTF8Encoding.UTF8.GetBytes(msg + "\r\n");
			if (LogFile == null)
				InitLog();
			lock (LogFile)
				LogFile.BeginWrite(data, 0, data.Length, (ac) =>
				{
					LogFile.EndWrite(ac);
				}, LogFile);
		}


		public static string LogPath()
		{
			return Path.GetTempPath() + "/LucyGrid/log";
		}

		public void Dispose()
		{
			LogFile.Dispose();
		}

		public static void SerializeJSON(Stream s, object data)
		{
			var json = Newtonsoft.Json.JsonConvert.SerializeObject(data);
			var bytes = UTF8Encoding.UTF8.GetBytes(json);
			s.Write(bytes, 0, bytes.Length);
		}

		public static void Finalize()
		{
			NotifyLog("UTILS,INFO", "Finalize called");
			// Flush logger
			finalize = true;
			msgQueuedThred.Join(3000);
		}
	}
}
